iT邦幫忙

2

將C++程式封裝(打包)成DLL給C#呼叫,包含字串、陣列、指標與結構的傳遞

  • 分享至 

  • xImage
  •  

C++包成DLL給C#呼叫這點,網路上已經有很多相關文章了,可惜那些文章都只是傳遞基礎的int, float, double等,缺乏指標資料的傳遞方式,因此來做個紀錄方便自己的後續回顧以及幫助他人

以下以Visual Studio 2022 Community做示範


C++端

開啟VS,選擇建立新的專案
開啟VS

選擇C++、Windows、程式庫 > 動態連結程式庫(DLL)
選擇動態連結程式庫

選擇喜歡的位置儲存
選擇儲存專案位置

將專案修改為Release版本(因為Debug性能很差),然後選擇目標平台(x86或x64)
選擇Release和x64

對方案右鍵 > 屬性
方案屬性

C/C++ > 前置處理器 > 前置處理器定義 > 新增一個定義名(CPP_DLL_EXPORT),名稱可自定義
並且新增_CRT_SECURE_NO_WARNINGS這個定義,關掉VS的C4996警告
定義狀態

在方案總管 > 專案 > 標頭檔右鍵 > 加入 > 新增項目 > 標頭檔,建立一個名為DllTest.h的檔案
建立Header
因為DllTest.h內有#pragma once,所以要把DllTest.h加入到pch.h中
pch.h

打開DllTest.h,輸入以下程式碼
以下程式碼示範了int, float, string(const char pointer), struct資料的傳輸

#pragma once

#ifdef CPP_DLL_EXPORT
#define CPP_DLL_API __declspec(dllexport)
#else
#define CPP_DLL_API __declspec(dllimport)
#endif

namespace NS1
{
	namespace NS2
	{
#pragma pack(1)
		typedef struct
		{
			int value1;
			float value2;
		} MyValue;

		CPP_DLL_API int AddInt(int a, int b);
		CPP_DLL_API float AddFloat(float a, float b);
		CPP_DLL_API void PrintString(const char* str);
		CPP_DLL_API const char* StringMerge(const char* str1, const char* str2);
		CPP_DLL_API int* SetArray(int size);
		CPP_DLL_API void SetStruct(MyValue* value);
	}
}

namespace非必要,不過這個只是為了做教學示範,所以就把它也加進來了~

在方案總管 > 專案 > 資源檔右鍵 > 加入 > 新增項目 > 標頭檔,建立一個名為DllTest.cpp的檔案
建立Source

打開DllTest.cpp,輸入以下程式碼

#include "pch.h"
#include <string.h> //strcat
#include <stdlib.h> //malloc
#include <iostream> //cout

int NS1::NS2::AddInt(int a, int b)
{
	return a + b;
}

float NS1::NS2::AddFloat(float a, float b)
{
	return a + b;
}

void NS1::NS2::PrintString(const char* str)
{
	std::cout << str << std::endl;
}

const char* NS1::NS2::StringMerge(const char* str1, const char* str2)
{
	char* res;
	res = (char*)malloc(sizeof(char) * (strlen(str1) + strlen(str2)));
	strcat(res, str1);
	strcat(res, str2);
	return const_cast<const char*>(res);
}

int* NS1::NS2::SetArray(int size)
{
	int* arr;
	arr = (int*)malloc(sizeof(int) * size);
	for (int i = 0; i < size; i++) arr[i] = i + 1;
	return arr;
}

void NS1::NS2::SetStruct(NS1::NS2::MyValue* value)
{
	value->value1 = 10;
	value->value2 = 20;
}

點擊建置 > 建置方案(或者快捷鍵Ctrl + Shift + B)
建置專案

到專案的輸出資料夾(CppDLL/x64/Release),將CppDLL.dll複製到桌面上
接著打開Terminal(cmd或pwoershell皆可),使用cd切換到Visual Studio 2022 Community的工具資料夾內
並使用dumpbin.exe工具查詢封裝後的DLL函數名稱

cd 'C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\bin\Hostx64\x64'
dumpbin.exe /EXPORTS C:\Users\yuyang\Desktop\CppDLL.dll
dumpbin查詢

上圖中等號左邊的就是函數名稱,這些名稱請記住,稍後會在C#中用到


C#

一樣開啟VS,選擇建立新的專案 > 選擇C#、Windows、主控台、.Net Framework
然後將專案切換為和DLL相同的平台(x86或x64),切換完畢後先偵錯 > 開始偵錯(或者快捷鍵F5),讓系統創建好資料夾路徑

將前面放到桌面的那個DLL放到系統創建好的資料夾中
將DLL放到目的地

接著開始編寫程式碼
程式中MethodName內的資訊請改為dumpbin得到的字串資料

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace CallDLL
{
	internal class Program
	{
		private const string DLL_PATH = "CppDLL.dll";
		
		//使用dumpbin.exe取得的函數名
		private struct MethodName
		{
			public const string AddInt = "?AddInt@NS2@NS1@@YAHHH@Z";
			public const string AddFlost = "?AddFloat@NS2@NS1@@YAMMM@Z";
			public const string PrintString = "?PrintString@NS2@NS1@@YAXPEBD@Z";
			public const string SetArray = "?SetArray@NS2@NS1@@YAPEAHH@Z";
			public const string SetStruct = "?SetStruct@NS2@NS1@@YAXPEAUMyValue@12@@Z";
			public const string StringMerge = "?StringMerge@NS2@NS1@@YAPEBDPEBD0@Z";
		}

		[StructLayout(LayoutKind.Sequential)]
		struct MyValue
		{
			public int value1;
			public float value2;
		}

		static void Main(string[] args)
		{
			Console.WriteLine(AddInt(10, 20));
			
			Console.WriteLine(AddFloat(11.1f, 22.2f));
			
			PrintString("Hello~ PrintString");

			unsafe
			{
				IntPtr ptrStr = StringMerge("Hello~ ", "StringMerge");
				string result = Marshal.PtrToStringAnsi(ptrStr);
				Console.WriteLine(result);
				Marshal.FreeCoTaskMem(ptrStr);
			}

			IntPtr ptrArray = SetArray(5);
			int[] array = new int[5];
			Marshal.Copy(ptrArray, array, 0, array.Length);
			foreach(var value in array) Console.WriteLine(value);

			MyValue mv;
			SetStruct(out mv);
			Console.WriteLine(mv.value1);
			Console.WriteLine(mv.value2);

			Console.ReadKey();//pause terminal
		}

		//C#的函數名稱可以和C++那邊的函數名稱不同,但傳入和傳出的參數數量與類型必須相同
		//函數前面必須使用static extern (與此程式是否在static void Main內被呼叫無關)
		[DllImport(DLL_PATH, EntryPoint=MethodName.AddInt)]
		private static extern int AddInt(int a, int b);

		[DllImport(DLL_PATH, EntryPoint=MethodName.AddFlost)]
		private static extern float AddFloat(float a, float b);

		[DllImport(DLL_PATH, EntryPoint=MethodName.PrintString)]
		private static extern void PrintString(string s);

		[DllImport(DLL_PATH, EntryPoint=MethodName.StringMerge)]
		unsafe private static extern IntPtr StringMerge(string str1, string str2);

		[DllImport(DLL_PATH, EntryPoint = MethodName.SetArray)]
		private static extern IntPtr SetArray(int size);

		[DllImport(DLL_PATH, EntryPoint = MethodName.SetStruct)]
		private static extern void SetStruct(out MyValue value);
	}
}

測試

搞定~


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言